package com.tobeface.wechat.request;

import com.tobeface.modules.helper.JsonHelper;
import com.tobeface.modules.lang.IOs;
import com.tobeface.modules.lang.Langs;
import com.tobeface.modules.lang.Requires;

import javax.net.ssl.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Map;

/**
 * @author loudyn
 */
public class URLConnectionWeChatRequestSinker implements WeChatRequestSinker {

    private static final String APPLICATION_JSON = "application/json";
    private static SSLSocketFactory sslSocketFactory;

    static {
        try {

            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, new TrustManager[]{new X509TrustManager() {
                @Override
                public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
                }

                @Override
                public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
                }

                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }
            }}, null);

            sslSocketFactory = sslContext.getSocketFactory();
        } catch (Exception e) {
            throw Langs.toUncheck(e);
        }

    }

    @Override
    public WeChatResponse sinkRequest(WeChatRequest request) {
        try {

            HttpURLConnection conn = createHttpURLConnection(request);
            byte[] content = writeRequest(conn, request);
            return WeChatResponse.of(content);
        } catch (Exception e) {
            throw Langs.toUncheck(e);
        }
    }

    private HttpURLConnection createHttpURLConnection(WeChatRequest request) throws IOException {

        URL url = new URL(request.getQueryUrl());
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        if (conn instanceof HttpsURLConnection) {
            ((HttpsURLConnection) conn).setSSLSocketFactory(sslSocketFactory);
        }

        conn.setRequestMethod(request.getVerb().name());
        for (Map.Entry<String, String> entry : request.getHeaders().entrySet()) {
            conn.setRequestProperty(entry.getKey(), entry.getValue());
        }
        conn.setRequestProperty("User-Agent", WeChatRequest.USER_AGENT);
        conn.setRequestProperty("Accept", APPLICATION_JSON);
        conn.setRequestProperty("Content-Type", APPLICATION_JSON);

        if (request.getConnectTimeoutMillis() > 0) {
            conn.setConnectTimeout(request.getConnectTimeoutMillis());
        }
        if (request.getWaitTimeoutMillis() > 0) {
            conn.setReadTimeout(request.getWaitTimeoutMillis());
        }

        return conn;
    }

    private byte[] writeRequest(HttpURLConnection conn, WeChatRequest request) {

        InputStream in = null;
        OutputStream out = null;
        try {

            in = conn.getInputStream();
            out = conn.getOutputStream();

            conn.setDoOutput(true);
            Object entity = request.getEntity();
            if (null != entity && request.getVerb() == WeChatRequest.Verb.POST) {
                IOs.write(out, JsonHelper.toBytes(entity));
            }

            Map<String, String> bodyParams = request.getBodyParams();
            if (!bodyParams.isEmpty() && request.getVerb() == WeChatRequest.Verb.POST) {
                IOs.write(out, JsonHelper.toBytes(bodyParams));
            }

            ByteArrayOutputStream buf = new ByteArrayOutputStream();
            IOs.piping(in, buf);
            return buf.toByteArray();
        } catch (Exception e) {
            throw Langs.toUncheck(e);
        } finally {
            conn.disconnect();
            IOs.freeQuietly(in, out);
        }
    }

    @Override
    public WeChatResponse sinkRequest(WeChatRequestStrategy strategy, WeChatRequest request) {
        Requires.notNull(strategy);
        Requires.notNull(request);
        return strategy.perform(this, request);
    }
}
